﻿Imports System.Collections
Imports System.IO
Imports System.Text
Imports System.Drawing
Imports System.Drawing.Text
Imports System.Runtime.InteropServices
Imports System.Runtime.Serialization
Imports System.Runtime.Serialization.Formatters
Imports System.Runtime.Serialization.Formatters.Binary
Imports System.Diagnostics
Imports ICSharpCode.SharpZipLib.GZip

Public Class ModUtils

    ' Public Arrays containing all known file extensions
    ' that Dawn of War 2 uses. This array will be used 
    ' to determine what files show in the File List
    ' and what files will be included when exporting
    ' and packing up a mod.

    Public Shared ReadOnly TextExts As String() = {".rbf", ".scar", ".lua", ".shader", ".ai", ".txt"}

    Public Shared ReadOnly NonTextExts As String() = {".dds", ".tga", ".abp", ".model", ".hkx", ".marker", ".motiontree", ".fxo", ".gfx"}

    ' Public Subroutine to fill a TreeView ( passed by reference ) with the directories
    ' and files (provided that are in the AryExts) starting at the Directory passed in
    Public Shared Sub BuildTree(ByVal WDir As String, ByRef tvMod As TreeView)

        ' Make out Extension List and add the arrays
        Dim ExtList As New List(Of String)
        ExtList.AddRange(TextExts)
        ExtList.AddRange(NonTextExts)

        ' Check to see if WDir is a valid Directory
        If System.IO.Directory.Exists(WDir) = False Then
            ' If the Directory doesn't exist, inform the user and BAIL!
            MsgBox("Directory Does Not Exist", MsgBoxStyle.Critical, "ERROR")
            Exit Sub
        End If

        ' Create a DirectoryInfo based on the path WDir
        Dim WDirInfo As New DirectoryInfo(WDir)

        ' Create the Tree View Root Node
        Dim RootNode As New TreeNode

        ' Add the Root Node to the Tree View
        tvMod.Nodes.Add(RootNode)

        ' Set the Root Node's properties
        RootNode.Name = WDirInfo.FullName
        RootNode.Text = WDirInfo.Name

        'Start Buildling the Nodes for the Tree
        BuildNodes(WDirInfo, RootNode, ExtList)
    End Sub

    Private Shared Sub BuildNodes(ByVal WDir As DirectoryInfo, ByRef NodeTree As TreeNode, ByVal Extensions As List(Of String))
        ' For every Directory in the Directory WDir (The start point)
        For Each iDir In WDir.GetDirectories
            ' Define a new Node for the Directory
            Dim dirNode As New TreeNode
            ' Set the Node to the Directory (fill in the properties)
            dirNode.Name = iDir.FullName
            dirNode.Text = iDir.Name

            ' Call the Function Recursively to continue building the Tree 
            ' Naving down to the next directory and start over
            ' passing in the Directory we just found and the node we just made to hold it.
            BuildNodes(iDir, dirNode, Extensions)

            ' Finally as we unroll the Recursive Calls, Add the Node to the Tree
            NodeTree.Nodes.Add(dirNode)
        Next
        ' For Each File in the Directory WDir
        For Each iFile In WDir.GetFiles
            'Loop through the extensions in the ExtensionArray

            For Each iString In Extensions.ToArray
                ' If the File's Extension MATCHES an extension in the Array
                If iString = iFile.Extension Then
                    ' Make a Node for the File
                    Dim fileNode As New TreeNode

                    ' Set it's properties
                    fileNode.Name = iFile.FullName
                    fileNode.Text = iFile.Name

                    If Array.IndexOf(NonTextExts, iFile.Extension) >= 0 Then
                        ' If it's a non Text File, Use Binary Image
                        fileNode.ImageIndex = 3
                        fileNode.SelectedImageIndex = 3
                    Else
                        ' Otherwise, use Document Image
                        fileNode.ImageIndex = 2
                        fileNode.SelectedImageIndex = 2
                    End If

                    ' and the node to our Node Tree
                    NodeTree.Nodes.Add(fileNode)
                End If
            Next
        Next
    End Sub

    <Serializable()> Structure ModFile
        'Name of the Mod
        Dim ModName As String

        'Version of the Modification
        Dim ModVersion As String

        'Who made the Mod
        Dim ModAuthor As String

        'The contact Email for the Mod Author (optional)
        Dim ModAuthorEmail As String

        'The homepage for the Mod (optional)
        Dim ModAuthorHomepage As String

        'Does the mod contain a Custom UCS File?
        Dim ModUseCustomUCS As Boolean

        'Does the mod contain a DataSGA?
        Dim ModHasDataSGA As Boolean

        'Does the mod contain a AttribSGA?
        Dim ModHasAttribSGA As Boolean

        'Binary UCS Data
        Dim ModUCSData As Byte()

        'Binary Data SGA
        Dim DataSGA As Byte()

        'Binary Attrib SGA
        Dim AttribSGA As Byte()

    End Structure

    Public Sub CreateModFile(ByVal mFile As ModFile, ByVal FileName As String)
        Dim MS As New MemoryStream
        Dim BF As New BinaryFormatter
        Try
            BF.Serialize(MS, mFile)
            FileOps.WriteCompressedFile(MS, FileName)
        Catch ex As Exception
            MsgBox(ex.Message)
        End Try
    End Sub

    Public Function ReadModFile(ByVal FileName As String) As ModFile
        Dim MFile As New ModFile
        Dim MS As New MemoryStream
        Dim BF As New BinaryFormatter
        Try
            FileOps.ReadCompressedFile(MS, FileName)
            MFile = DirectCast(BF.Deserialize(MS), ModFile)
        Catch ex As Exception
            MsgBox(ex.Message)
        End Try
        Return MFile
    End Function

End Class

Public Class FileOps

    ' ShortCut Declartion
    Const chrQuote As Char = """"
    ' Buffer Size Declaration
    Const BUFFER_SIZE As Int32 = 1024

    ' Convert RBF Working Dir to TXT in Temp Dir
    Public Shared Function RBFtoText(ByVal RBFName As String) As String
        'Open a File Info to the RBF File pointd by the string we got in.
        Dim rbfFile As New FileInfo(RBFName)

        ' Make Sure the file exists
        If rbfFile.Exists Then

            ' Now we want to convert this file into
            ' our text file for us to edit
            ' This will require RBFConv.exe
            ' Convert to txt and send the caller
            ' back the fullname of the new file

            ' Get Our System Temp Path
            Dim TempPath As String = My.Computer.FileSystem.SpecialDirectories.Temp

            ' Make a New Directory inside the TempPath for our file (shouldn't be two files in the same dir with the same name)
            Dim tDir As New DirectoryInfo(TempPath & "\DoW2Suite\" & rbfFile.DirectoryName.Substring(3))
            If Not tDir.Exists Then tDir.Create()

            Dim txtFileString As String = tDir.FullName & rbfFile.Name.Substring(0, rbfFile.Name.Length - 3) & "txt"

            ' Copy the File (over write if it exists) to the new Location
            ' My.Computer.FileSystem.CopyFile(rbfFile.FullName, tDir.FullName & "\" & rbfFile.Name, True)

            ' Check for required file rbfconv
            If Not My.Computer.FileSystem.FileExists(My.Application.Info.DirectoryPath & "\sup\rbfconv\rbfconv.exe") Then
                MsgBox("Required file, RBFCONV.EXE, not found")
                Return ""
                Exit Function
            End If

            ' Dimark out the Process and Process Start Info
            Dim iProcess As New Process
            Dim ipStart As New ProcessStartInfo

            'Make a CommandLine Builder
            Dim clBuilder As New StringBuilder
            clBuilder.Append(chrQuote + My.Application.Info.DirectoryPath)
            clBuilder.Append("\sup\rbfconv\rbfconv.exe" + chrQuote)
            clBuilder.Append(" -s -i ")
            clBuilder.Append(chrQuote + rbfFile.FullName + chrQuote)
            clBuilder.Append(" -o ")
            clBuilder.Append(chrQuote + txtFileString + chrQuote)

            ' <path>\rbfconv -i filein.rbf -o fileout.txt -s
            ipStart.FileName = clBuilder.ToString
            ' Don't Show a Window for the PRocess
            ipStart.CreateNoWindow = True
            ipStart.UseShellExecute = False
            'ipStart.RedirectStandardOutput = True
            'ipStart.RedirectStandardError = True
            'Set the StartInfo's
            iProcess.StartInfo = ipStart
            iProcess.Start()
            iProcess.WaitForExit()

            'MsgBox(iProcess.StandardError.ReadToEnd(), MsgBoxStyle.OkOnly)
            'MsgBox(iProcess.StandardOutput.ReadToEnd(), MsgBoxStyle.OkOnly)

            ' check for fileout.txt's existance if it does, return it.
            If My.Computer.FileSystem.FileExists(txtFileString) Then
                Return txtFileString
            Else
                Return ""
            End If
        Else
            Return ""
        End If
    End Function

    Public Shared Sub TXTtoRBF(ByVal txtFile As String, ByVal rbfFile As String)
        Dim clConvertCommand As New StringBuilder
        clConvertCommand.Append(chrQuote + My.Application.Info.DirectoryPath)
        clConvertCommand.Append("\sup\rbfconv\rbfconv.exe" + chrQuote)
        clConvertCommand.Append(" -R -i ")
        clConvertCommand.Append(chrQuote + txtFile + chrQuote)
        clConvertCommand.Append(" -o ")
        clConvertCommand.Append(chrQuote + rbfFile + chrQuote)

        ' <path>\rbfconv -i filein.rbf -o fileout.txt -R
        ' Dim out the Process and Process Start Info
        Dim iProcess As New Process
        Dim ipStart As New ProcessStartInfo
        ipStart.FileName = clConvertCommand.ToString
        ' Don't Show a Window for the PRocess
        ipStart.CreateNoWindow = True
        ipStart.UseShellExecute = False
        'ipStart.RedirectStandardOutput = True
        'ipStart.RedirectStandardError = True
        'Set the StartInfo's
        iProcess.StartInfo = ipStart
        iProcess.Start()
        iProcess.WaitForExit()
    End Sub

    ' Convert TXT in Temp Dir to RBF in Working Dir

    ' Write Compressed File (SharpZip Library)
    Public Shared Sub WriteCompressedFile(ByVal inStream As System.IO.MemoryStream, ByVal FileName As String)
        'Use the File Output Stream
        Using fStream As New System.IO.FileStream(FileName, IO.FileMode.Create, IO.FileAccess.Write)
            'Use the Compression Stream and attach it to the output stream
            Using nGStream As New ICSharpCode.SharpZipLib.GZip.GZipOutputStream(fStream)
                'Rewind the Input Stream
                inStream.Position = 0
                'Create the buffer, make the writes, and transfer the data from the memory stream
                'into the compression stream
                Dim buff(BUFFER_SIZE) As Byte
                Dim bytesRead As Integer = inStream.Read(buff, 0, BUFFER_SIZE)
                While bytesRead <> 0
                    nGStream.Write(buff, 0, bytesRead)
                    bytesRead = inStream.Read(buff, 0, BUFFER_SIZE)
                End While
            End Using
        End Using
        'Rewind the Input Stream
        inStream.Position = 0
    End Sub

    Public Shared Sub WriteCompressedFileNative(ByVal inStream As System.IO.MemoryStream, ByVal FileName As String)
        'Use the File Output Stream
        Using fStream As New System.IO.FileStream(FileName, IO.FileMode.Create, IO.FileAccess.Write)
            'Use the Compression Stream and attach it to the output stream
            Using gStream As New System.IO.Compression.GZipStream(fStream, IO.Compression.CompressionMode.Compress, False)
                'Rewind the Input Stream
                inStream.Position = 0
                'Create the buffer, make the writes, and transfer the data from the memory stream
                'into the compression stream
                Dim buff(BUFFER_SIZE) As Byte
                Dim bytesRead As Integer = inStream.Read(buff, 0, BUFFER_SIZE)
                While bytesRead <> 0
                    gStream.Write(buff, 0, bytesRead)
                    bytesRead = inStream.Read(buff, 0, BUFFER_SIZE)
                End While
            End Using
        End Using
        'Rewind the Input Stream
        inStream.Position = 0
    End Sub

    Public Shared Sub ReadCompressedFile(ByRef mStream As System.IO.MemoryStream, ByVal FileName As String)
        'Open the File Input Stream
        Using fStreamIn As New System.IO.FileStream(FileName, IO.FileMode.Open, IO.FileAccess.Read)
            'Open a new decompression stream and attach to input stream
            Using gZipStream As New ICSharpCode.SharpZipLib.GZip.GZipInputStream(fStreamIn) 'System.IO.Compression.GZipStream(fStreamIn, IO.Compression.CompressionMode.Decompress, False)
                'Rewind the memory stream
                mStream.Position = 0
                'Create the Buffer and start tranfering data from the decompression stream
                'and write into the memory stream 
                Dim buff(BUFFER_SIZE) As Byte
                Dim bytesRead As Integer = gZipStream.Read(buff, 0, BUFFER_SIZE)
                While bytesRead <> 0
                    mStream.Write(buff, 0, bytesRead)
                    bytesRead = gZipStream.Read(buff, 0, BUFFER_SIZE)
                End While
            End Using
        End Using
        'rewind the memory stream again 
        mStream.Position = 0
    End Sub

End Class

Public Class CustomFont

    'PRIVATE FONT COLLECTION TO HOLD THE DYNAMIC FONT

    Private _pfc As PrivateFontCollection = Nothing

    Public ReadOnly Property GetInstance(ByVal Size As Single, ByVal style As FontStyle) As Font
        Get
            'IF THIS IS THE FIRST TIME GETTING AN INSTANCE
            'LOAD THE FONT FROM RESOURCES
            If _pfc Is Nothing Then LoadFont()
            'RETURN A NEW FONT OBJECT BASED ON THE SIZE AND STYLE PASSED IN
            Return New Font(_pfc.Families(0), Size, style)
        End Get
    End Property


    Private Sub LoadFont()
        Try
            'INIT THE FONT COLLECTION
            _pfc = New PrivateFontCollection
            'LOAD MEMORY POINTER FOR FONT RESOURCE
            Dim fontMemPointer As IntPtr = Marshal.AllocCoTaskMem(My.Resources.monaco.Length) 'consola.Length)
            'COPY THE DATA TO THE MEMORY LOCATION
            Marshal.Copy(My.Resources.monaco, 0, fontMemPointer, My.Resources.monaco.Length) 'consola.Length)
            'LOAD THE MEMORY FONT INTO THE PRIVATE FONT COLLECTION
            _pfc.AddMemoryFont(fontMemPointer, My.Resources.monaco.Length)  ' consola.Length)
            'FREE UNSAFE MEMORY
            Marshal.FreeCoTaskMem(fontMemPointer)
        Catch ex As Exception
            'ERROR LOADING FONT. HANDLE EXCEPTION HERE
            MsgBox(ex.ToString)
        End Try
    End Sub

End Class


Public Class UCSToDataSet

    Public Shared Sub Save(ByVal File As String, ByVal TableName As String, ByVal delimiter As String, ByVal ds As DataSet, Optional ByRef Progress As System.Windows.Forms.ProgressBar = Nothing)
        Using r As New StreamWriter(File)
            Dim dr As DataRow() = ds.Tables(TableName).Select()
            If Progress IsNot Nothing Then
                Progress.Value = 0
                Progress.Maximum = ds.Tables(TableName).Rows.Count
            End If
            For Each drr As DataRow In dr
                If Progress IsNot Nothing Then
                    Progress.Value = Progress.Value + 1
                End If
                r.WriteLine(drr.Item(0).ToString() & vbTab & drr.Item(1).ToString())
            Next
        End Using
    End Sub

    Public Shared Function Load(ByVal File As String, ByVal TableName As String, ByVal delimiter As String, Optional ByRef Progress As System.Windows.Forms.ProgressBar = Nothing) As DataSet
        'The DataSet to Return
        Dim result As New DataSet()

        'Open the file in a stream reader.
        Dim s As New StreamReader(File)

        'Split the first line into the columns       
        Dim columns As String() = {"Numeric ID", "String Text"}  's.ReadLine().Split(delimiter.ToCharArray())

        'Add the new DataTable to the RecordSet
        result.Tables.Add(TableName)

        'Cycle the colums, adding those that don't exist yet 
        'and sequencing the one that do.

        For Each col As String In columns
            Dim added As Boolean = False
            Dim [next] As String = ""
            Dim i As Integer = 0
            While Not added
                'Build the column name and remove any unwanted characters.
                Dim columnname As String = col + [next]
                columnname = columnname.Replace("#", "")
                columnname = columnname.Replace("'", "")
                columnname = columnname.Replace("&", "")

                'See if the column already exists
                If Not result.Tables(TableName).Columns.Contains(columnname) Then
                    'if it doesn't then we add it here and mark it as added
                    result.Tables(TableName).Columns.Add(columnname)
                    added = True
                Else
                    'if it did exist then we increment the sequencer and try again.
                    i += 1
                    [next] = "_" & i.ToString()
                End If
            End While
        Next

        'Read the rest of the data in the file.        
        Dim AllData As String = s.ReadToEnd()

        'Close Data stream
        s.Close()

        'Split off each row at the Carriage Return/Line Feed
        'Default line ending in most windows exports.  
        'You may have to edit this to match your particular file.
        'This will work for Excel, Access, etc. default exports.

        Dim rows As String() = AllData.Split(vbCrLf.ToCharArray) 'vbCr.ToCharArray & vbLf.ToCharArray())
        If Progress IsNot Nothing Then
            Progress.Maximum = rows.Length
            Progress.Value = 0
        End If
        'Now add each row to the DataSet        
        For Each r As String In rows
            'Split the row at the delimiter.
            Dim items As String() = r.Split(delimiter.ToCharArray())
            If Progress IsNot Nothing Then
                Progress.Value = Progress.Value + 1
            End If
            'Add the item
            If items.Length > 1 Then
                result.Tables(TableName).Rows.Add(items)
            End If
        Next
        'Return the imported data.  
        Return result
    End Function
End Class